//
//  CorelPS2PDF.cpp : Defines the entry point for the console application.
//
//  Copyright (C) 2010 Corel Corporation
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License version 2 as
//  published by the Free Software Foundation. See the file License.txt
//  for more details.
//


#include "stdafx.h"
#include <vector>
#include <map>
#include <algorithm>

#define PS2PDF_SUCCESS 1
#define PS2PDF_ERROR   0


// function declarations.
bool IsGhostcriptAvailable( CString &strApplicationName );
bool GetGhostscriptExecutable( const CString &strGsDll, CString &strApplicationName );
CString GetPreferredVersion();

int _tmain( int argc, _TCHAR* argv[] )
{
	if ( 3 != argc )
	{
		_tprintf_s( _T("usage: CorelPS2PDF input.ps output.pdf\n") );
		return PS2PDF_ERROR;
	}

	CString strApplicationName;
	if ( !IsGhostcriptAvailable( strApplicationName ) )
	{
		return PS2PDF_ERROR;
	}

	// set up the command line.
	CString strCommandLine = _T("-q -dSAFER -dNOPAUSE -dBATCH -dEPSCrop -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dAutoFilterColorImages=false -dAutoFilterGrayImages=false -dColorImageFilter=/FlateEncode -dGrayImageFilter=/FlateEncode -sOutputFile=");
	// "output file" "input file"
	strCommandLine.AppendFormat(_T("\"%s\" \"%s\""), argv[2], argv[1]);

	STARTUPINFO si;
	::ZeroMemory( &si, sizeof( STARTUPINFO ) );
	si.cb = sizeof( STARTUPINFO );

	PROCESS_INFORMATION pi;
	::ZeroMemory( &pi, sizeof( PROCESS_INFORMATION ) );

	// execute the process.
	INT nRet = ::CreateProcess( strApplicationName, strCommandLine.GetBuffer(), NULL, NULL, FALSE, CREATE_NO_WINDOW | NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi );
	if ( 0 != nRet )
	{
		WaitForSingleObject( pi.hProcess, INFINITE );
		
		DWORD dwGSExitCode = 0;
		BOOL bRetProcess = GetExitCodeProcess( pi.hProcess, &dwGSExitCode );
		if( !bRetProcess || dwGSExitCode != 0 ) nRet = 0;

		CloseHandle( pi.hProcess );
		CloseHandle( pi.hThread );		

	}
	strCommandLine.ReleaseBuffer();

	return ( nRet > 0 ? PS2PDF_SUCCESS : PS2PDF_ERROR );
}


// Get the version of ghostscript to be used as specified by the user
CString GetPreferredVersion()
{
	CString strVersion;
	CRegKey key;
	if( ERROR_SUCCESS == key.Open( HKEY_CURRENT_USER, _T("Software\\Corel\\CorelPS2PDF"), KEY_READ ) )
	{
		ULONG ulSize = 100;
		LONG lRes = key.QueryStringValue(_T("PreferredVersion"), strVersion.GetBuffer(ulSize), &ulSize);
		strVersion.ReleaseBuffer(ulSize - 1);
		if( ERROR_SUCCESS != lRes )
		{
			strVersion.Empty();
		}
		else
		{
			strVersion.MakeLower();
		}
	}
	return strVersion;
}

std::vector<CString> GetVersionParts(CString const& strVersion)
{
	// Split the version string into parts delimited by periods
	std::vector<CString> parts;
	int nStart = 0;
	CString strToken = strVersion.Tokenize(_T("."), nStart);
	while(!strToken.IsEmpty())
	{
		parts.push_back(strToken);
		strToken = strVersion.Tokenize(_T("."), nStart);
	}
	return parts;
}

int CompareVersions(CString const& strVersion1, CString const& strVersion2)
{
	// First try literal comparison
	int nRet = strVersion1.Compare(strVersion2);
	if(nRet != 0)
	{
		std::vector<CString> version1 = GetVersionParts(strVersion1);
		std::vector<CString> version2 = GetVersionParts(strVersion2);
		size_t nMaxPartCount = (std::max)(version1.size(), version2.size());
		if(nMaxPartCount > 0)
		{
			nRet = 0;
		}

		for(size_t i = 0; i < nMaxPartCount && (nRet == 0); i++)
		{
			CString strPart1 = (i < version1.size()) ? version1[i] : _T("");
			CString strPart2 = (i < version2.size()) ? version2[i] : _T("");
			if(i == 0)
			{
				// Only the first part (major version) should be compared numericly
				nRet = _tstoi(strPart1) - _tstoi(strPart2);
			}
			else
			{
				nRet = strPart1.Compare(strPart2);
			}
		}
	}
	return nRet;
}

void EnumGhostcriptVersions(std::map<CString, CString>& map, REGSAM samDesired)
{
	CRegKey key;
	if( ERROR_SUCCESS == key.Open(HKEY_LOCAL_MACHINE, _T("Software\\GPL Ghostscript"), samDesired) )
	{
		for(DWORD iIndex = 0;; iIndex++)
		{
			CString strVersion;
			DWORD dwLen = MAX_PATH;
			LONG lRes = key.EnumKey(iIndex, strVersion.GetBuffer(dwLen), &dwLen);
			strVersion.ReleaseBuffer(dwLen);
			if( ERROR_SUCCESS != lRes)
			{
				break;
			}

			CRegKey subKey;
			if( ERROR_SUCCESS == subKey.Open(key, strVersion, samDesired) )
			{
				CString strPath;
				dwLen = MAX_PATH;
				lRes = subKey.QueryStringValue(_T("GS_DLL"), strPath.GetBuffer(dwLen), &dwLen);
				strPath.ReleaseBuffer(dwLen - 1);
				if( ERROR_SUCCESS == lRes )
				{
					strVersion.MakeLower();
					map.insert(std::make_pair(strVersion, strPath));
				}
			}
		}
	}
}

std::map<CString, CString> GetGhostcriptVersions()
{
	std::map<CString, CString> versions;

	// Try the native registry view (32-bit for x86 process or 64-bit for x64 process)
	REGSAM samDesired = KEY_READ;
	EnumGhostcriptVersions(versions, samDesired);

	// On x64 OS, try the redirected view of registry (32 for x64 and 64 for x86 process)
	SYSTEM_INFO info = {};
	GetNativeSystemInfo(&info);
	if(info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 || info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64)
	{
#ifdef _WIN64
		samDesired |= KEY_WOW64_32KEY;
#else
		samDesired |= KEY_WOW64_64KEY;
#endif
		EnumGhostcriptVersions(versions, samDesired);
	}

	return versions;
}

//
// IsGhostcriptAvailable looks up the Registry to find out if the
// Ghostscript is installed. If it is not, the function returns
// false. If it is installed, it will parse the registry keys to
// find out the location where it is installed, setting the return
// value to true, and also returning the pathname to the Ghostscript
// executable.
//
bool IsGhostcriptAvailable( CString &strApplicationName )
{
	bool bRet = false;
	std::map<CString, CString> versions = GetGhostcriptVersions();
	if(!versions.empty())
	{
		CString strPreferredVersion = GetPreferredVersion();
		if(!strPreferredVersion.IsEmpty())
		{
			std::map<CString, CString>::const_iterator p = versions.find(strPreferredVersion);
			if(p != versions.end())
			{
				bRet = GetGhostscriptExecutable( p->second, strApplicationName );
			}
		}

		if(!bRet)
		{
			// Preferred version not found, get the highest version available
			CString strHighestVersion;
			CString strPath;
			CString strPossibleExeName;
			for(std::map<CString, CString>::const_iterator p = versions.begin(); p != versions.end(); ++p)
			{
				if(strHighestVersion.IsEmpty() || CompareVersions(strHighestVersion, p->first) < 0)
				{
					// only use versions of ghostscript for which we can find the executable:
					CString strThrowAwayAppName;
					bool foundGhostscriptExe = GetGhostscriptExecutable( p->second, strPossibleExeName );
					if (foundGhostscriptExe)
					{
						strHighestVersion = p->first;
						strPath = p->second;
						strApplicationName = strPossibleExeName;
						bRet = true;
					}
				}
			}
		}
	}	

	return bRet;
}


//
// GetGhostscriptExecutable breaks up the pathname of the Ghostscript DLL
// to obrtain the name of the Ghostscript executable. If successfull
// it returns true, and false otherwise.
//
bool GetGhostscriptExecutable( const CString &strGsDll, CString &strApplicationName )
{
	bool bRet = false;

	_TCHAR szGsBinDrive[ _MAX_PATH ] = {};
	_TCHAR szGsBinDir[ _MAX_PATH ] = {};


	// split off the file name.
	if ( 0 == _tsplitpath_s( strGsDll, szGsBinDrive, _MAX_PATH, szGsBinDir, _MAX_PATH, NULL, 0, NULL, 0 ) )
	{
		strApplicationName  = szGsBinDrive;
		strApplicationName += szGsBinDir;
		LPCTSTR possibleExeNames[] = {
			_T("gswin64c.exe"),
			_T("gswin32c.exe"),
		};

		for (int i = 0; i < _countof(possibleExeNames); ++i)
		{
			CString candidate = strApplicationName + possibleExeNames[i];
			_stat64i32 info;
			int stat_check = _tstat( candidate, &info );
			// if we can get the stat on a file, it obviously exists:
			if (stat_check == 0)
			{
				strApplicationName = candidate;
				bRet = true;
				break;
			}
		}
	}

	return bRet;
}
